﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net.Sockets;
using System.Net;
using ProtocoloPropietario;
using System.Threading;
using System.Security.Cryptography;

namespace Servidor
{
    class TransferFile
    {
        private string ruta;
        private Servidor servidor;
        private Cliente cliente;
        private Socket socCliente;
        private Socket socServidor;
        private TcpClient _tcpClient;


        public TransferFile(Servidor serv, Cliente cli, Socket soCli)
        {
            cliente = cli;
            servidor = serv;
            socCliente = soCli;
            socServidor = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        public TransferFile(Cliente cli, TcpClient tcpClient)
        {
            cliente = cli;
            tcpClient.Connect(cliente.Ip, cliente.Puerto);
        }

        public bool Subir(MensajeProtocolo msj)
        {
            IPEndPoint ipEnd = new IPEndPoint(servidor.IpCliente, servidor.NumPuertoCliente);
            try
            {
                IPEndPoint ipEndCliente = new IPEndPoint(cliente.Ip, cliente.Puerto);
                socCliente = new Socket(ipEndCliente.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                socCliente.Connect(ipEndCliente);
                List<string> infoMsj = msj.InfoDatos();
                string nomArchivo = infoMsj[0];
                long largoArchivo = long.Parse(infoMsj[1]);
                string mD5 = infoMsj[2];
                ruta = Program.rutaCarpetaArchivos + "\\" + nomArchivo;
                FileStream fsNew = new FileStream(ruta, FileMode.Create, FileAccess.Write);
                using (fsNew)
                {
                    int bytes = 0;
                    int tamanioBuff  = 10010;
                    byte[] buff;
                    int n = 0;
                    Console.WriteLine("Descargando de cliente " + cliente.Ip + ".....( 0% )");
                    do
                    {
                        if (largoArchivo < tamanioBuff - 10)
                        {
                            buff = new byte[largoArchivo + 10];
                        }
                        else
                        {
                            buff = new byte[tamanioBuff];
                        }
                        socCliente.Receive(buff);
                        MensajeProtocolo msjRespuesta = new MensajeProtocolo(Protocolo.Header.RES,Protocolo.Comando.CLIENTE_SUBIDA,1,"");
                        if (MensajeProtocolo.ValidarMensaje(buff))
                        {
                            MensajeProtocolo msjRecibido = MensajeProtocolo.ObtenerMensajeProtocolo(buff);

                            byte[] datosAEscribir = msjRecibido.Datos;
                            bytes += msjRecibido.Largo;
                            fsNew.Write(datosAEscribir, 0, msjRecibido.Largo);

                        }
                        else
                        {
                            msjRespuesta = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.CLIENTE_SUBIDA, 0, "");
                        }
                        socCliente.Send(msjRespuesta.ConvertirAByte());

                        n++;
                        if (n == 5) n = 0;
                        if (n == 0) Console.WriteLine("Descargando de cliente "+ cliente.Ip +".....( " + bytes * 100 / largoArchivo + "% )");
                    }
                    while (bytes < largoArchivo);

                }
                Console.WriteLine("Descargando de cliente " + cliente.Ip + ".....( 100% )");

                fsNew.Close();
                FileStream archivoBajado = new FileStream(ruta, FileMode.Open, FileAccess.Read);
                MD5 mD5Subido = new MD5CryptoServiceProvider();
                byte[] md5Buff = mD5Subido.ComputeHash(archivoBajado);
                string textoMD5 = (System.Text.Encoding.ASCII.GetString(md5Buff, 0, md5Buff.Length));

                socServidor.Close();

                if (textoMD5.Equals(mD5))
                {
                    MensajeProtocolo msjAEnviar = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.CLIENTE_SUBIDA, 1, "1");
                    byte[] protocoloAEnviar = msjAEnviar.ConvertirAByte();
                    socCliente.Send(protocoloAEnviar, protocoloAEnviar.Length, SocketFlags.None);
                    socCliente.Close();
                    return true;
                }
                else
                {
                    MensajeProtocolo msjAEnviar = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.CLIENTE_SUBIDA, 1, "0");
                    byte[] protocoloAEnviar = msjAEnviar.ConvertirAByte();
                    socCliente.Send(protocoloAEnviar, protocoloAEnviar.Length, SocketFlags.None);
                    socCliente.Close();
                    File.Delete(ruta);
                    return false;
                }
            }
            catch (FileNotFoundException)
            {
                socCliente.Close();
                socServidor.Close();
                File.Delete(ruta);
                return false;
            }
            catch (SocketException)
            {
                socCliente.Close();
                socServidor.Close();
                File.Delete(ruta);
                return false;
            }
        }

        public bool Bajar(MensajeProtocolo msj)
        {
            try
            {

                if (msj.Cmd == Protocolo.Comando.CLIENTE_BAJADA_INICIO)
                {
                    NombresArchivos();
                    IPEndPoint ipEndAEscuchar = new IPEndPoint(servidor.IpCliente, servidor.NumPuertoCliente);
                    byte[] buff = new byte[60];
                    socCliente.Receive(buff);
                    MensajeProtocolo msjRecibido = MensajeProtocolo.ObtenerMensajeProtocolo(buff);
                    List<string> infoRes = msjRecibido.InfoDatos();
                    ruta = (Program.rutaCarpetaArchivos + "\\" + infoRes[0]); 
                    FileInfo fi = new FileInfo(ruta);
                    long tamanioArchivo = fi.Length;
                    MensajeProtocolo msjAEnviar = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.CLIENTE_BAJADA_INICIO,fi.Length.ToString().Length, fi.Length.ToString());
                    socCliente.Send(msjAEnviar.ConvertirAByte());
                    MandarDePos(0);
                }
                else if (msj.Cmd == Protocolo.Comando.CLIENTE_BAJADA_POS)
                {
                    List<string> infoMsj = msj.InfoDatos();
                    ruta = (Program.rutaCarpetaArchivos + "\\" + infoMsj[0]);
                    IPEndPoint ipEndAEscuchar = new IPEndPoint(cliente.Ip, cliente.Puerto);
                    socCliente.Connect(ipEndAEscuchar);
                    FileStream fs = new FileStream(ruta, FileMode.Open, FileAccess.Read);
                    MensajeProtocolo msjAEnviar = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.CLIENTE_BAJADA_POS, fs.Length.ToString().Length, fs.Length.ToString());
                    socCliente.Send(msjAEnviar.ConvertirAByte());
                    MandarDePos(long.Parse(infoMsj[1]));
                }

                return true;
            }
            catch (FileNotFoundException)
            {
                socCliente.Close();
                socServidor.Close();
                return false;
            }
            catch (SocketException)
            {
                socCliente.Close();
                socServidor.Close();
                return false;
            }
        }

        private void MandarDePos(long pos)
        {
            FileStream fileASubir = new FileStream(ruta, FileMode.Open, FileAccess.Read);
            using (fileASubir)
            {
                long tamanioArchivo = fileASubir.Length;
                fileASubir.Position = pos;
                //lee segun el tamanio del protocolo
                long tamDatos = 10000;
                byte[] buff = new byte[tamDatos];
                int readCount = fileASubir.Read(buff, 0, (int)tamDatos);//Cambiar a ancho de protocolo cuando este
                Thread.Sleep(250);
                Console.WriteLine("Enviando al cliente " + cliente.Ip + "el archivo " + Path.GetFileName(ruta) + ".....( 0% )");
                int tiempoUltimaEspera = Environment.TickCount;
                long cantBytes = pos;
                int n = 0;
                while (readCount > 0)
                {
                    MensajeProtocolo msjAEnviar = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.CLIENTE_BAJADA_POS, readCount, buff);
                    byte[] protocoloAEnviar = msjAEnviar.ConvertirAByte();
                    socCliente.Send(protocoloAEnviar, protocoloAEnviar.Length, SocketFlags.None);
                    cantBytes += buff.Length;

                    if (Throttling(tiempoUltimaEspera, protocoloAEnviar.Length))
                    {
                        tiempoUltimaEspera = Environment.TickCount;
                    }

                    byte[] buffRespuesta = new byte[10];
                    socCliente.Receive(buffRespuesta);
                    MensajeProtocolo msjRes = MensajeProtocolo.ObtenerMensajeProtocolo(buffRespuesta);

                    if (msjRes.Largo == 1)
                    {
                        if (tamanioArchivo < cantBytes + buff.Length && (tamanioArchivo - cantBytes) > 0)
                        {
                            buff = new byte[tamanioArchivo - cantBytes];
                            tamDatos = (int)tamanioArchivo - cantBytes;
                        }
                        else
                        {
                            buff = new byte[tamDatos];
                        }
                        readCount = fileASubir.Read(buff, 0, (int)tamDatos);
                    }
                    else
                    {
                        cantBytes -= buff.Length;
                    }
                    n++;
                    if (n == 5) n = 0;
                    if (n == 0) Console.WriteLine("Enviando al cliente " + cliente.Ip + "el archivo " + Path.GetFileName(ruta) + ".....( " + cantBytes * 100 / tamanioArchivo + "% )");
                }
                Console.WriteLine("Enviando al cliente " + cliente.Ip + "el archivo " + Path.GetFileName(ruta) + ".....( 100% )");
                socCliente.Close();
                socServidor.Close();
                fileASubir.Close();
            }

        }
        private void NombresArchivos()
        {
            DirectoryInfo di = new DirectoryInfo(Program.rutaCarpetaArchivos);
            string strMsj = "";
            foreach (FileInfo f in di.GetFiles())
            {
                strMsj += f.Name + "\0";
            }
            socCliente.Close();
            IPEndPoint ipEndAEscuchar = new IPEndPoint(cliente.Ip, cliente.Puerto);
            socCliente = new Socket(ipEndAEscuchar.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            socCliente.Connect(ipEndAEscuchar);
            MensajeProtocolo msj = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.CLIENTE_BAJADA_INICIO, strMsj.Length, strMsj);
            byte[] b = msj.ConvertirAByte();
            socCliente.Send(b, b.Length, SocketFlags.None);
        }

        private bool Throttling(long tiempoUltimaEspera, int cantBytes)
        {
            long milSegTransc = Environment.TickCount - tiempoUltimaEspera;

            if (milSegTransc > 0)
            {

                long bps = cantBytes * 1000L / milSegTransc;

                if (bps > servidor.AnchoBandaPorCliente)
                {
                    // Calcula el tiempo a dormir
                    long tiempoADormirTotal = cantBytes * 1000L / servidor.AnchoBandaPorCliente;
                    int tiempoADormir = (int)(tiempoADormirTotal - milSegTransc);

                    if (tiempoADormir > 1)
                    {
                        try
                        {
                            Thread.Sleep(tiempoADormir);
                        }
                        catch (ThreadAbortException)
                        {

                        }
                        return true;
                    }

                }
            }
            return false;
        }

        public bool RecibirSubidaArchivoServidor(string nombreArchivo)
        {
            bool ok = false;
            string msj = "";
            try
            {
                _tcpClient.Connect(cliente.Ip, cliente.Puerto);
                NetworkStream networkStream = _tcpClient.GetStream();                

                if (networkStream.CanWrite && networkStream.CanRead)
                {
                    //Le respondo por Tcp al servidor cliente para que me envie el archivo, pasandole el nombre que me envio por UDP anteriormente
                    MensajeProtocolo msjRespuesta = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.SERVIDOR_CONEXION_SUBIDA, nombreArchivo.Length, nombreArchivo);

                    byte[] paqueteRes = msjRespuesta.ConvertirAByte();
                    networkStream.Write(paqueteRes, 0, paqueteRes.Length);

                    byte[] bytes = null;
                    long largoArchivo = 0;
                    FileStream fsNew = null;

                    //Aca recibo el protocolo con comando del nombre del archivo, largo y md5
                    bool fileInfo = false;
                    while (fileInfo)
                    {
                        // Reads the NetworkStream into a byte buffer.
                        bytes = new byte[_tcpClient.ReceiveBufferSize];
                        int BytesRead = networkStream.Read(bytes, 0, (int)_tcpClient.ReceiveBufferSize);

                        //Aca verifico que me haya mandado el nombre del archivo y le respondo que me llego para que empiece a mandarlo
                        MensajeProtocolo msgProtocoloFileInfo = MensajeProtocolo.ObtenerMensajeProtocolo(bytes);
                        if (msgProtocoloFileInfo.Cmd.Equals(Protocolo.Comando.SERVIDOR_SUBIDA_INFO) && msgProtocoloFileInfo.Header.Equals(Protocolo.Header.REQ))
                        {
                            List<string> infoMsj = msgProtocoloFileInfo.InfoDatos();
                            largoArchivo = long.Parse(infoMsj[0]);
                            ruta = Program.rutaCarpetaArchivos + "\\" + nombreArchivo;
                            fsNew = new FileStream(ruta, FileMode.Create, FileAccess.Write);
                            fileInfo = true;

                            //Respondo que me llego correctamente
                            msj = Protocolo.OK;
                            msjRespuesta = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.SERVIDOR_SUBIDA_INFO, msj.Length, msj);
                            paqueteRes = msjRespuesta.ConvertirAByte();
                            networkStream.Write(paqueteRes, 0, paqueteRes.Length);
                        }
                        else
                        {
                            //Respondo que me llego mal y me lo tienen que enviar nuevamente
                            msj = Protocolo.ERROR;
                            msjRespuesta = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.SERVIDOR_SUBIDA_INFO, msj.Length, msj);
                            paqueteRes = msjRespuesta.ConvertirAByte();
                            networkStream.Write(paqueteRes, 0, paqueteRes.Length);
                        }
                    }

                    using (fsNew)
                    {
                        int bytesLength = 0;
                        int tamanioBuff = 10010;
                        byte[] buff = new byte[tamanioBuff];
                        int n = 0;
                        Console.WriteLine("Descargando de servidor " + cliente.Ip + ".....( 0% )");
                        do
                        {
                            if (largoArchivo < tamanioBuff - 10)
                            {
                                buff = new byte[largoArchivo + 10];
                            }
                            else
                            {
                                buff = new byte[tamanioBuff];
                            }
                            networkStream.Read(buff, 0, (int)_tcpClient.ReceiveBufferSize);

                            if (MensajeProtocolo.ValidarMensaje(buff))
                            {
                                MensajeProtocolo msjRecibido = MensajeProtocolo.ObtenerMensajeProtocolo(buff);

                                byte[] datosAEscribir = msjRecibido.Datos;
                                bytesLength += msjRecibido.Largo;
                                fsNew.Write(datosAEscribir, 0, msjRecibido.Largo);

                                msj = Protocolo.OK;
                                msjRespuesta = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.SERVIDOR_SUBIDA, msj.Length, msj);
                            }
                            else
                            {
                                msj = Protocolo.ERROR;
                                msjRespuesta = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.SERVIDOR_SUBIDA, msj.Length, msj);
                            }
                            paqueteRes = msjRespuesta.ConvertirAByte();
                            networkStream.Write(paqueteRes, 0, paqueteRes.Length);

                            n++;
                            if (n == 5) n = 0;
                            if (n == 0) Console.WriteLine("Descargando de servidor " + cliente.Ip + ".....( " + bytesLength * 100 / largoArchivo + "% )");
                        }
                        while (bytesLength < largoArchivo);

                    }
                    Console.WriteLine("Descargando de servidor " + cliente.Ip + ".....( 100% )");


                    FileStream archivoBajado = new FileStream(ruta, FileMode.Open, FileAccess.Read);

                    ok = true;
                    networkStream.Close();
                    _tcpClient.Close();  
                }
                else if (!networkStream.CanRead)
                {
                    Console.WriteLine("You can not write data to this stream");
                    _tcpClient.Close();
                }
                else if (!networkStream.CanWrite)
                {
                    Console.WriteLine("You can not read data from this stream");
                    _tcpClient.Close();
                }
            }
            catch (FileNotFoundException)
            {
                _tcpClient.Close();
            }
            catch (SocketException)
            {
                _tcpClient.Close();
                Console.WriteLine("Sever IP:" + cliente.Ip + " , Puerto: " + cliente.Puerto + " no se puede acceder!");
            }
            catch (System.IO.IOException)
            {
                _tcpClient.Close();
                Console.WriteLine("Sever IP:" + cliente.Ip + " , Puerto: " + cliente.Puerto + " no se puede acceder!");
            }
            catch (Exception e)
            {
                _tcpClient.Close();
                Console.WriteLine(e.ToString());
            }

            return ok;
        }

    }
}
